//=============================================================================
// LazyParser.NET - C# Expression Parser for .NET 2.0 
//
// Copyright (c) 2008 Philippe Leybaert
//
// Permission is hereby granted, free of charge, to any person obtaining a copy 
// of this software and associated documentation files (the "Software"), to deal 
// in the Software without restriction, including without limitation the rights 
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
// copies of the Software, and to permit persons to whom the Software is 
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in 
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//=============================================================================

using System;
using System.Collections.Generic;
using System.Globalization;

namespace Activa.LazyParser
{
    public class CSharpParser : ExpressionParser
    {
        private static readonly NumberFormatInfo _numberFormat;

        public MissingFieldHandler MissingMember;
        public MissingFieldHandler InterceptMember;

        static CSharpParser()
        {
            _numberFormat = new NumberFormatInfo();

            _numberFormat.NumberDecimalSeparator = ".";
            _numberFormat.NumberGroupSeparator = ",";
            _numberFormat.NumberGroupSizes = new int[] { 3 };
        }

        public CSharpParser()
        {
            AddTerm(@"""([^""\\]|\\['""\\0abfnrtv]|\\x[a-fA-F0-9][a-fA-F0-9]{0,3})*""", EvalStringLiteral);
            AddTerm(@"'([^'\\]|\\['""\\0abfnrtv]|\\x[a-fA-F0-9][a-fA-F0-9]{0,3})'", EvalCharLiteral);

            AddUnaryOperator(@"\([a-zA-Z_@][a-zA-Z0-9_]*\)(?=\s*[a-zA-Z0-9_])", 19, EvalTypeCast);

            AddLeftParen(@"\(");
            AddRightParen(@"\)");
            AddLeftParen(@"\[");
            AddRightParen(@"\]");
            AddArgumentSeparator(",");

            AddBinaryOperator(@"&&", 10, EvalShortcutOperator);
            AddBinaryOperator(@"\|\|", 9, EvalShortcutOperator);
            AddBinaryOperator(@"\?\?", 8, EvalCoalesce);

            AddTernaryOperator(@"\?", @"\:", 7, OperatorAssociativity.Right, EvalTernary);

            AddBinaryOperator(@"==|!=", 14, EvalOperator);
            AddBinaryOperator(@"\.", 20, EvalDotOperator);
            AddUnaryOperator(@"!", 19, EvalUnary);
            AddUnaryOperator(@"\-", 19, EvalUnary);
            AddUnaryOperator(@"\~", 19, EvalUnary);
            AddBinaryOperator(@"\*|/|%", 18, EvalOperator);
            AddBinaryOperator(@"\+", 17, EvalOperator);
            AddBinaryOperator(@"\-", 17, EvalOperator);
            AddBinaryOperator(@"\<\<|\>\>", 16, EvalOperator);
            AddBinaryOperator(@"\<=|\>=", 15, EvalOperator);
            AddBinaryOperator(@"\<|\>", 15, EvalOperator);
            AddBinaryOperator(@"\bas|is\b", 15, EvalIsAsOperator);
            AddBinaryOperator(@"&", 13, EvalOperator);
            AddBinaryOperator(@"\^", 12, EvalOperator);
            AddBinaryOperator(@"\|", 11, EvalOperator);
            AddBinaryOperator(@"=", 6, OperatorAssociativity.Right, EvalAssignment);

            AddTerm("typeof", EvalTypeOf);
            AddTerm(@"new\s+[a-zA-Z_][a-zA-Z0-9_]*", EvalConstructor);

            AddTerm(@"[a-zA-Z_@][a-zA-Z0-9_@]*", EvalVarName);
            AddTerm(@"\d+(?:\.\d+)?([uU][lL]|[lL][uU]|[FfDdMmUuLl])?", EvalNumber);

            FunctionEvaluator = EvalFunction;
            FunctionCallPrecedence = 20;
        }

        protected virtual Expression EvalTypeCast(string token, Expression[] terms)
        {
            return new TypeCastExpression(new VariableExpression(token.Substring(1, token.Length - 2).Trim()), terms[0]);
        }

        protected virtual Expression EvalIsAsOperator(string token, Expression[] terms)
        {
            if (token == "as")
                return new AsExpression(terms[0], terms[1]);

            if (token == "is")
                return new IsExpression(terms[0], terms[1]);

            return null;
        }

        protected virtual Expression EvalTernary(string token, Expression[] terms)
        {
            return new ConditionalExpression(terms[0], terms[1], terms[2]);
        }

        private static char UnEscape(string s)
        {
            if (s.Length == 1)
                return s[0];

            if (s.Length == 2)
            {
                switch (s[1])
                {
                    case '\\':
                    case '\"':
                    case '\'':
                        return s[1];
                    case '0':
                        return (char)0;
                    case 'a':
                        return '\a';
                    case 'b':
                        return '\b';
                    case 'f':
                        return '\f';
                    case 'n':
                        return '\n';
                    case 'r':
                        return '\r';
                    case 't':
                        return '\t';
                    case 'v':
                        return '\v';
                    default:
                        throw new ArgumentException();
                }
            }
            else
            {
                return (char)Convert.ToUInt16(s.Substring(2), 16);
            }
        }

        protected virtual Expression EvalTypeOf(string token, Expression[] terms)
        {
            return new TypeOfExpression();
        }

        protected virtual Expression EvalCharLiteral(string token, Expression[] terms)
        {
            return Exp.Value(UnEscape(token.Substring(1, token.Length - 2)));
        }

        protected virtual Expression EvalNumber(string token, Expression[] terms)
        {
            string s = token;

            Type type = null;

            if (!char.IsDigit(s[s.Length - 1]))
            {
                string suffix = "" + char.ToUpper(s[s.Length - 1]);

                s = s.Remove(s.Length - 1);

                if (!char.IsDigit(s[s.Length - 1]))
                {
                    suffix = char.ToUpper(s[s.Length - 1]) + suffix;

                    s = s.Remove(s.Length - 1);
                }

                switch (suffix)
                {
                    case "M":
                        type = typeof(decimal);
                        break;
                    case "D":
                        type = typeof(double);
                        break;
                    case "F":
                        type = typeof(float);
                        break;
                    case "L":
                        type = typeof(long);
                        break;
                    case "U":
                        type = typeof(uint);
                        break;
                    case "LU":
                    case "UL":
                        type = typeof(ulong);
                        break;
                }
            }

            if (type != null)
                return new ValueExpression(Convert.ChangeType(s, type, _numberFormat), type);

            if (s.LastIndexOf('.') >= 0)
            {
                return Exp.Value(Convert.ToDouble(s, _numberFormat));
            }
            else
            {
                long n = Convert.ToInt64(s);

                if (n > Int32.MaxValue || n < Int32.MinValue)
                    return Exp.Value(n);
                else
                    return Exp.Value((int)n);
            }
        }

        protected virtual Expression EvalVarName(string token, Expression[] terms)
        {
            return new VariableExpression(token);
        }

        protected virtual Expression EvalFunction(string token, Expression[] terms)
        {
            Expression[] parameters = new Expression[terms.Length - 1];

            Array.Copy(terms, 1, parameters, 0, parameters.Length);

            if (token == "[")
            {
                return new IndexExpression(terms[0], parameters);
            }
            else
            {
                return new CallExpression(terms[0], parameters);
            }
        }

        protected virtual Expression EvalCoalesce(string token, Expression[] terms)
        {
            return new CoalesceExpression(terms[0], terms[1]);
        }

        protected virtual Expression EvalShortcutOperator(string token, Expression[] terms)
        {
            if (token == "&&")
                return new AndAlsoExpression(terms[0], terms[1]);

            if (token == "||")
                return new OrElseExpression(terms[0], terms[1]);

            return null;
        }

        protected virtual Expression EvalUnary(string token, Expression[] terms)
        {
            if (token == "!")
                return new NegationExpression(terms[0]);

            if (token == "-")
                return new UnaryMinusExpression(terms[0]);

            if (token == "~")
                return new BitwiseComplementExpression(terms[0]);

            return null;
        }

        protected virtual Expression EvalOperator(string token, Expression[] terms)
        {
            return Exp.Op(token, terms[0], terms[1]);
        }

        protected virtual Expression EvalAssignment(string token, Expression[] terms)
        {
            return new AssignmentExpression(terms[0], terms[1]);
        }

        protected virtual Expression EvalStringLiteral(string token, Expression[] terms)
        {
            string s = token.Substring(1, token.Length - 2);

            if (s.IndexOf('\\') < 0)
                return Exp.Value(s);

            string output = "";

            bool inEscape = false;
            string hexString = null;

            for (int i = 0; i < s.Length; i++)
            {
                char c = s[i];

                if (inEscape)
                {
                    if (c == 'x')
                    {
                        hexString = "";
                        continue;
                    }

                    if (hexString == null && (c != 'x' || c != 'X'))
                    {
                        output += UnEscape("\\" + c);
                        inEscape = false;
                        continue;
                    }

                    if (hexString == null)
                    {
                        inEscape = false;
                    }
                    else
                    {
                        if (((char.ToLower(c) < 'a' || char.ToLower(c) > 'f') && (c < '0' || c > '9')) || hexString.Length == 4)
                        {
                            output += UnEscape("\\x" + hexString);
                            inEscape = false;
                            hexString = null;
                        }
                        else
                        {
                            hexString += c;
                            continue;
                        }
                    }
                }

                if (c != '\\')
                {
                    output += c;

                    continue;
                }

                inEscape = true;
            }

            return Exp.Value(output);
        }

        protected virtual Expression EvalDotOperator(string token, Expression[] terms)
        {
            VariableExpression varExpression = terms[1] as VariableExpression;

            if (varExpression == null)
                throw new ArgumentException();

            return new FieldExpression(terms[0], varExpression.VarName, MissingMember, InterceptMember);
        }

        protected virtual Expression EvalConstructor(string token, Expression[] terms)
        {
            string className = token.Substring(3).Trim();

            return new ConstructorExpression(new VariableExpression(className), terms);
        }
    }
}
